りおんクロニクル


C# × CSV高速処理(大量データ対応)|読み書き・分割・集計の実務ガイド【2026年版】

Home【2026年版】C# / .NET入門と実践ガイド|基礎・業務アプリ開発・SQLite連携まで体系的に解説

CSVは「軽い・シンプル・どこでも開ける」ため、 業務システムのインポート・エクスポートで今も主役です。 一方で、10万件〜100万件クラスになると、 素直な実装ではメモリ不足・処理遅延・フリーズが発生します。

この記事でわかること
・C#でのCSV基本読み書き
・大量データを扱うときの「やってはいけない実装」
・ストリーミング処理(1行ずつ処理)
・分割出力・フィルタ・集計のパターン
・非同期処理と進捗表示
・業務アプリ向けベストプラクティス

1. CSV処理で「やってはいけない」典型パターン

大量CSVでまず避けるべきは、次のような実装です。

■ 1-1. 全行を一気にメモリに載せる

// 悪い例:100万行を全部メモリに読み込む
var lines = File.ReadAllLines("data.csv"); // メモリ圧迫
foreach (var line in lines)
{
    // 処理...
}

10万行程度ならまだしも、100万行を超えると メモリ使用量が一気に跳ね上がり、GCも頻発します。

■ 1-2. 1行ごとにファイルを開閉する

// 悪い例:毎回Open/Close
foreach (var item in items)
{
    File.AppendAllText("out.csv", item.ToCsvLine() + Environment.NewLine);
}

ファイルI/Oのオーバーヘッドで極端に遅くなります。

2. 基本:ストリーミングで1行ずつ処理する

大量CSVの基本は、「読みながら処理する」ストリーミング方式です。 メモリに全行を載せず、1行ずつ読みながら処理していきます。

■ 2-1. 読み込みの基本パターン

using var reader = new StreamReader("data.csv");
string? line;
while ((line = reader.ReadLine()) != null)
{
    // 1行ずつ処理
    var cols = line.Split(',');
    // cols[0], cols[1], ...
}

この方式なら、100万行でもメモリ使用量はほぼ一定です。

■ 2-2. 書き込みの基本パターン

using var writer = new StreamWriter("out.csv", false); // 上書き
foreach (var item in items)
{
    var line = string.Join(",", item.Id, item.Name, item.Amount);
    writer.WriteLine(line);
}

1つのStreamWriterを使い回すことで、I/Oを最小限に抑えます。

3. ヘッダー付きCSVの処理

業務CSVでは、1行目にヘッダーがあるケースがほとんどです。 ヘッダーを読み飛ばす or 利用するパターンを押さえておきます。

■ 3-1. ヘッダーを読み飛ばす

using var reader = new StreamReader("data.csv");

// 1行目ヘッダーを読み飛ばす
reader.ReadLine();

string? line;
while ((line = reader.ReadLine()) != null)
{
    var cols = line.Split(',');
    // 処理...
}

■ 3-2. ヘッダーをキーにして列を参照する

using var reader = new StreamReader("data.csv");

var headerLine = reader.ReadLine() ?? "";
var headers = headerLine.Split(',');

// 列名 → インデックスのマップ
var index = headers
    .Select((name, i) => new { name, i })
    .ToDictionary(x => x.name, x => x.i);

string? line;
while ((line = reader.ReadLine()) != null)
{
    var cols = line.Split(',');
    var id   = cols[index["Id"]];
    var name = cols[index["Name"]];
    var amt  = decimal.Parse(cols[index["Amount"]]);
}

列順が変わる可能性があるCSVでは、この方式が安全です。

4. 大量CSVのフィルタ・集計処理

「条件に合う行だけ抽出したい」「合計値を出したい」 といった処理も、ストリーミングで十分対応できます。

■ 4-1. 条件フィルタして別CSVに出力

using var reader = new StreamReader("input.csv");
using var writer = new StreamWriter("filtered.csv", false);

var header = reader.ReadLine();
writer.WriteLine(header); // ヘッダーをそのままコピー

string? line;
while ((line = reader.ReadLine()) != null)
{
    var cols = line.Split(',');
    var amount = decimal.Parse(cols[3]); // 例:4列目が金額

    if (amount >= 100000) // 10万円以上だけ出力
    {
        writer.WriteLine(line);
    }
}

■ 4-2. 合計・件数などの集計

using var reader = new StreamReader("input.csv");
reader.ReadLine(); // ヘッダー読み飛ばし

decimal total = 0;
int count = 0;

string? line;
while ((line = reader.ReadLine()) != null)
{
    var cols = line.Split(',');
    var amount = decimal.Parse(cols[3]);
    total += amount;
    count++;
}

Console.WriteLine($"件数: {count}, 合計: {total:#,##0}");

この方式なら、100万行でもメモリをほとんど使わずに集計できます。

5. 大きなCSVを分割する(ファイル分割)

「1ファイルが大きすぎて扱いづらい」「メール添付できない」 といった場合は、行数で分割するのが定番です。

■ 5-1. N行ごとにファイル分割

int maxLinesPerFile = 50000; // 5万行ごとに分割
int fileIndex = 1;
int currentLineCount = 0;

using var reader = new StreamReader("big.csv");
string? header = reader.ReadLine();

StreamWriter? writer = null;

void OpenNewFile()
{
    writer?.Dispose();
    string fileName = $"big_part_{fileIndex:D3}.csv";
    writer = new StreamWriter(fileName, false);
    writer.WriteLine(header);
    currentLineCount = 0;
    fileIndex++;
}

OpenNewFile();

string? line;
while ((line = reader.ReadLine()) != null)
{
    if (currentLineCount >= maxLinesPerFile)
    {
        OpenNewFile();
    }

    writer!.WriteLine(line);
    currentLineCount++;
}

writer?.Dispose();

これで「1ファイルあたり5万行」の分割CSVが生成できます。

6. 非同期処理と進捗表示(WPF / コンソール)

大量CSV処理は時間がかかるため、 UIスレッドをブロックしない非同期実行が重要です。

■ 6-1. 非同期でCSV処理を実行

public async Task ProcessCsvAsync(string path, IProgress<int>? progress = null)
{
    var totalLines = File.ReadLines(path).Count(); // ざっくり行数取得
    int processed = 0;

    using var reader = new StreamReader(path);
    reader.ReadLine(); // ヘッダー

    string? line;
    while ((line = await reader.ReadLineAsync()) != null)
    {
        // 行の処理...
        processed++;
        if (processed % 1000 == 0)
        {
            int percent = (int)(processed * 100.0 / totalLines);
            progress?.Report(percent);
        }
    }
}

■ 6-2. WPFでの進捗表示イメージ

public async Task RunAsync()
{
    IsBusy = true;
    var progress = new Progress<int>(p => Progress = p);
    await _csvService.ProcessCsvAsync("data.csv", progress);
    IsBusy = false;
}

「どれくらい進んでいるか」が見えるだけで、ユーザーのストレスは大きく減ります。

7. CSVライブラリを使うかどうかの判断

素の Split(',') でも十分ですが、 カンマ・ダブルクォート・改行を含むフィールドを正しく扱うには 専用ライブラリ(CsvHelperなど)を使うのが安全です。

■ ライブラリを使うべきケース

一方で、シンプルな社内CSVなら、素の実装でも十分なことが多いです。

8. 業務アプリ向けベストプラクティス

まとめ:CSV高速処理は“メモリを信じず、流しながら処理する”が正解

「CSVが重くて開けない」「アプリが固まる」 という現場の悩みに対して、 C#のストリーミング処理は非常に強力な解決策になります。 この記事をベースに、あなたの業務アプリに最適なCSV処理フローを設計してみてください。

前のページ  次のページ